package com.ayu.shortlink.engine.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.ayu.shortlink.engine.common.constant.RedisCacheConstant;
import com.ayu.shortlink.engine.common.convention.exception.ClientException;
import com.ayu.shortlink.engine.dao.entity.CouponTemplateDO;
import com.ayu.shortlink.engine.dao.entity.CouponTemplateRemindDO;
import com.ayu.shortlink.engine.dao.mapper.CouponTemplateRemindMapper;
import com.ayu.shortlink.engine.dto.req.CouponTemplateRemindCancelReqDTO;
import com.ayu.shortlink.engine.dto.req.CouponTemplateRemindCreateReqDTO;
import com.ayu.shortlink.engine.dto.req.CouponTemplateRemindFindAllReqDTO;
import com.ayu.shortlink.engine.dto.resp.CouponTemplateRemindFindAllRespDTO;
import com.ayu.shortlink.engine.mq.basics.event.CouponRemindEvent;
import com.ayu.shortlink.engine.mq.producer.CouponRemindProducer;
import com.ayu.shortlink.engine.mq.producer.CouponRemindProducer2;
import com.ayu.shortlink.engine.service.CouponTemplateRemindService;
import com.ayu.shortlink.engine.service.CouponTemplateService;
import com.ayu.shortlink.engine.util.CouponTemplateRemindUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Service
@RequiredArgsConstructor
public class CouponTemplateRemindServiceImpl extends ServiceImpl<CouponTemplateRemindMapper, CouponTemplateRemindDO> implements CouponTemplateRemindService {
    private final CouponTemplateRemindMapper couponTemplateRemindMapper;
    private final CouponRemindProducer couponRemindProducer;
    private final CouponRemindProducer2 couponRemindProducer2;
    private final CouponTemplateService couponTemplateService;
    private final StringRedisTemplate stringRedisTemplate;
    private final Integer MinuteUnit = 60000;

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void createCouponRemind(CouponTemplateRemindCreateReqDTO requestParam) {
        LambdaQueryWrapper<CouponTemplateRemindDO> queryWrapper = Wrappers.lambdaQuery(CouponTemplateRemindDO.class)
                .eq(CouponTemplateRemindDO::getUserId, requestParam.getUserId())
                .eq(CouponTemplateRemindDO::getCouponTemplateId, requestParam.getCouponTemplateId());
        CouponTemplateRemindDO couponTemplateRemindDO = couponTemplateRemindMapper.selectOne(queryWrapper);

        if(couponTemplateRemindDO == null){
            //情况1：当前时间可能已经超过了开抢时间，直接开抢失败即可
            Date curDate = new Date();
            if(DateUtil.compare(curDate, requestParam.getStartTime())<=0){
                throw new ClientException("已经过了预约时间");
            }
            CouponTemplateRemindDO remindDO = BeanUtil.toBean(requestParam, CouponTemplateRemindDO.class);
            remindDO.setInformation(CouponTemplateRemindUtil.compute(requestParam.getType(), requestParam.getRemindTime()));
            couponTemplateRemindMapper.insert(remindDO);
            //情况2：是在开抢前1小时之前预约的
            DateTime startBeforeOneHour = DateUtil.offsetHour(requestParam.getStartTime(), -1);
            if(DateUtil.compare(curDate,startBeforeOneHour) <= 0){
                //计算到开抢前1小时的发送时间
                long deliverTime = DateUtil.between(curDate, startBeforeOneHour, DateUnit.MS);
                CouponRemindEvent event = CouponRemindEvent.builder()
                        .user_id(requestParam.getUserId())
                        .couponTemplateId(requestParam.getCouponTemplateId())
                        .shopNumber(requestParam.getShopNumber())
                        .level(11)
                        .build();
                couponRemindProducer.sendMessage(event,deliverTime);
                return;
            }
            //情况3：是在开抢一个小时之内预约的
            Long betweenTime = DateUtil.between(curDate, requestParam.getStartTime(), DateUnit.MS);
            // 获取等级
            Integer level = computeLevel(betweenTime);
            // 根据等级去发送最近的时间
            DateTime targetTime = DateUtil.offsetMinute(requestParam.getStartTime(), -(level + 1) * 5);
            long deliverTime = DateUtil.between(curDate, targetTime, DateUnit.MS);
            CouponRemindEvent event = CouponRemindEvent.builder()
                    .user_id(requestParam.getUserId())
                    .couponTemplateId(requestParam.getCouponTemplateId())
                    .shopNumber(requestParam.getShopNumber())
                    .level(level)
                    .build();
            couponRemindProducer.sendMessage(event,deliverTime);
        }else {
            Long information = couponTemplateRemindDO.getInformation();

            Long compute = CouponTemplateRemindUtil.compute(requestParam.getType(), requestParam.getRemindTime());
            if ((information & compute) != 0) {
                throw new ClientException("你已经订阅该时间点");
            }
            couponTemplateRemindDO.setInformation(information|compute);
            couponTemplateRemindMapper.updateById(couponTemplateRemindDO);

            //进行删缓存操作
            stringRedisTemplate.delete(String.format(RedisCacheConstant.COUPON_REMIND_INFORMATION, requestParam.getUserId()));
        }
    }

    /**
     * 用户订阅抢优惠券V2
     * @param requestParam 订阅请求
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void createCouponRemind2(CouponTemplateRemindCreateReqDTO requestParam) {
        Date curDate = new Date();
        if(DateUtil.compare(curDate, requestParam.getStartTime())<=0){
            throw new ClientException("已经过了预约时间");
        }
        long deliverTime = DateUtil.between(curDate, requestParam.getStartTime(), DateUnit.MS);
        if (deliverTime < 0){
            throw new ClientException("已经过了预约时间");
        }
        int level = Math.min(computeLevel(deliverTime),11);

        LambdaQueryWrapper<CouponTemplateRemindDO> queryWrapper = Wrappers.lambdaQuery(CouponTemplateRemindDO.class)
                .eq(CouponTemplateRemindDO::getUserId, requestParam.getUserId())
                .eq(CouponTemplateRemindDO::getCouponTemplateId, requestParam.getCouponTemplateId());
        CouponTemplateRemindDO couponTemplateRemindDO = couponTemplateRemindMapper.selectOne(queryWrapper);

        // 为null 就肯定是第一次
        if(couponTemplateRemindDO == null){
            CouponTemplateRemindDO remindDO = BeanUtil.toBean(requestParam, CouponTemplateRemindDO.class);
            Long firStatus = CouponTemplateRemindUtil.compute(requestParam.getType(), requestParam.getRemindTime());
            remindDO.setInformation(firStatus);
            remindDO.setMessageStatus(firStatus);
            couponTemplateRemindMapper.insert(remindDO);

            // 发送定时任务
            CouponRemindEvent event = CouponRemindEvent.builder()
                    .user_id(requestParam.getUserId())
                    .couponTemplateId(requestParam.getCouponTemplateId())
                    .shopNumber(requestParam.getShopNumber())
                    .level(level)
                    .build();
            couponRemindProducer2.sendMessage(event, deliverTime);
        }else {
            Long information = couponTemplateRemindDO.getInformation();
            Long messageStatus = couponTemplateRemindDO.getMessageStatus();

            Long compute = CouponTemplateRemindUtil.compute(requestParam.getType(), requestParam.getRemindTime());
            if ((information & compute) != 0) {
                throw new ClientException("你已经订阅该时间点");
            }
            // 需要看是否发送过消息
            if (CouponTemplateRemindUtil.isSend(messageStatus, level)){
                // 证明已经发送过消息 那只需要改变状态即可
                couponTemplateRemindDO.setInformation(information|compute);
                couponTemplateRemindMapper.updateById(couponTemplateRemindDO);
                return;
            }
            // 执行到这 说明这个时间段还没发送过定时消息
            couponTemplateRemindDO.setInformation(information|compute);
            couponTemplateRemindDO.setMessageStatus(
                    CouponTemplateRemindUtil.computeNextMessageStatus(messageStatus, level));

            couponTemplateRemindMapper.updateById(couponTemplateRemindDO);
            // 发送定时任务
            CouponRemindEvent event = CouponRemindEvent.builder()
                    .user_id(requestParam.getUserId())
                    .couponTemplateId(requestParam.getCouponTemplateId())
                    .shopNumber(requestParam.getShopNumber())
                    .level(level)
                    .build();
            couponRemindProducer2.sendMessage(event, deliverTime);

            //进行删缓存操作
            stringRedisTemplate.delete(String.format(RedisCacheConstant.COUPON_REMIND_INFORMATION, requestParam.getUserId()));
       }
    }

    private Integer computeLevel(Long deliverTime){
        long minute = deliverTime / MinuteUnit;
        return Math.toIntExact(minute / 5);
    }

    @Override
    @Transactional
    public void cancelCouponRemind(CouponTemplateRemindCancelReqDTO requestParam) {
        LambdaQueryWrapper<CouponTemplateRemindDO> queryWrapper = Wrappers.lambdaQuery(CouponTemplateRemindDO.class)
                .eq(CouponTemplateRemindDO::getUserId, requestParam.getUserId())
                .eq(CouponTemplateRemindDO::getCouponTemplateId, requestParam.getCouponTemplateId());
        CouponTemplateRemindDO couponTemplateRemindDO = couponTemplateRemindMapper.selectOne(queryWrapper);

        if(couponTemplateRemindDO == null){
            throw new ClientException("取消失败，你没有订阅该时间点");
        }
        Long compute = CouponTemplateRemindUtil.compute(requestParam.getType(), requestParam.getRemindTime());
        Long information = couponTemplateRemindDO.getInformation();

        if ((information & compute) == 0) {
            throw new ClientException("取消失败，你没有订阅该时间点");
        }

        compute ^= information;
        queryWrapper.eq(CouponTemplateRemindDO::getInformation, information);
        if(compute.equals(0L)){
            if(couponTemplateRemindMapper.delete(queryWrapper) == 0){
                throw new ClientException("取消预约失败");
            }
            //进行删缓存操作
            stringRedisTemplate.delete(String.format(RedisCacheConstant.COUPON_REMIND_INFORMATION, requestParam.getUserId()));
        }else {
            couponTemplateRemindDO.setInformation(compute);
            if(couponTemplateRemindMapper.update(couponTemplateRemindDO, queryWrapper) == 0){
                throw new ClientException("取消预约失败");
            }
            //进行删缓存操作
            stringRedisTemplate.delete(String.format(RedisCacheConstant.COUPON_REMIND_INFORMATION, requestParam.getUserId()));
        }
    }

    @Override
    public List<CouponTemplateRemindFindAllRespDTO> findAllCouponRemind(CouponTemplateRemindFindAllReqDTO requestParam) {
        String couponRemindKey = String.format(RedisCacheConstant.COUPON_REMIND_INFORMATION, requestParam.getUserId());
        String json = stringRedisTemplate.opsForValue().get(couponRemindKey);
        if(StrUtil.isNotEmpty(json)){
            return JSON.parseArray(json,CouponTemplateRemindFindAllRespDTO.class);
        }

        LambdaQueryWrapper<CouponTemplateRemindDO> queryWrapper = Wrappers.lambdaQuery(CouponTemplateRemindDO.class)
                .eq(CouponTemplateRemindDO::getUserId, requestParam.getUserId());

        List<CouponTemplateRemindDO> couponTemplateRemindDOS = couponTemplateRemindMapper.selectList(queryWrapper);

        if(CollUtil.isEmpty(couponTemplateRemindDOS)){
            throw new ClientException("该用户无预约信息");
        }

        List<Long> couponTemplateIds = couponTemplateRemindDOS.stream().map(CouponTemplateRemindDO::getCouponTemplateId).toList();
        List<Long> shopNumberIds = couponTemplateRemindDOS.stream().map(CouponTemplateRemindDO::getShopNumber).toList();
        List<CouponTemplateDO> couponTemplateList = couponTemplateService.findCouponTemplate(couponTemplateIds, shopNumberIds);

        if(CollUtil.isEmpty(couponTemplateList)){
            throw new ClientException("该用户无预约信息");
        }

        List<CouponTemplateRemindFindAllRespDTO> result = BeanUtil.copyToList(couponTemplateList, CouponTemplateRemindFindAllRespDTO.class);

        //填充提醒信息
        result.forEach(each -> {
            couponTemplateRemindDOS.stream()
                    .filter(i -> i.getCouponTemplateId().equals(each.getId()))
                    .findFirst()
                    .ifPresent(i -> {
                        //进行预约信息填充
                        CouponTemplateRemindUtil.fillCouponRemindInformation(each,i.getInformation());
                    });
        });
        //添加到缓存
        stringRedisTemplate.opsForValue().set(couponRemindKey,JSON.toJSONString(result),30, TimeUnit.MINUTES);
        return result;
    }
}
